package com.lambo.maven.manager;

import com.lambo.maven.core.MavenRepositoryStore;
import com.lambo.maven.core.utils.IoUtil;
import com.lambo.maven.core.vo.MavenRequest;
import com.lambo.maven.core.vo.MavenResponse;
import com.lambo.maven.core.vo.SettingConfig;
import com.lambo.maven.manager.utils.HeadersFilter;
import com.sun.net.httpserver.HttpContext;
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpServer;
import java.io.IOException;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.util.Base64;
import java.util.concurrent.Executors;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * main.
 *
 * @author lambo
 * @date 2018/6/16
 */
public class MavenManagerRunner implements Runnable {
    private Logger logger = LoggerFactory.getLogger(getClass());

    private String config = "classpath:/maven.xml";

    @Override
    public void run() {
        SettingConfig settingConfig;
        try {
            settingConfig = SettingConfig.fromXML(IoUtil.readToByteBuffer(config));
            settingConfig.init();
            logger.debug("http port = {}, context = {}", settingConfig.httpPort, settingConfig.httpContext);
            logger.debug("activeProfiles = {}, localRepository = {}", settingConfig.activeProfiles, settingConfig.localRepository);
            for (SettingConfig.Repository repository : settingConfig.repository) {
                logger.debug("repository id = {}, name = {}, url = {}", repository.id, repository.name, repository.url);
            }
        } catch (IOException e) {
            e.printStackTrace();
            return;
        }
        MavenRepositoryStore store = new MavenRepositoryStore(settingConfig);
        HeadersFilter headersFilter = new HeadersFilter();
        HttpServer httpServer;
        try {
            //初始一个http服务. 最大并发接收100个请求.
            httpServer = HttpServer.create(new InetSocketAddress(settingConfig.httpPort), 100);
            //并发10个请求.
            httpServer.setExecutor(Executors.newFixedThreadPool(10));
            HttpContext context = httpServer.createContext(settingConfig.httpContext, (httpExchange -> handle(httpExchange, store)));
            // java 自带的http server 响应时会将 head 中的key 中第二段变成小写开头.如，http协议：Content-Length, 但httpServer 则返回 Content-length.
            // 有些http的请求端可能会无法处理.所以将http头部转化一下.
            context.getFilters().add(headersFilter);
        } catch (IOException e) {
           logger.error("create context failed, port = {}", settingConfig.httpPort, e);
            return;
        }
        httpServer.start();
        logger.info("server on port = {}", settingConfig.httpPort);
        System.out.println("use  = " + (System.currentTimeMillis() - Long.getLong("system.start")));
        for (SettingConfig.Repository repository : settingConfig.repository) {
            logger.info("http://{}:{}{}{}",settingConfig.httpDomain, settingConfig.httpPort,settingConfig.httpContext,repository.id);
        }
    }

    private void handle(HttpExchange httpExchange, MavenRepositoryStore store) throws IOException {
        byte[] data = null;
        boolean generateMd5Sha1 = false;
        String method = httpExchange.getRequestMethod();
        if ("PUT".equals(method) || "generate_md5_sha1=true".equals(httpExchange.getRequestURI().getQuery())) {//put需要验证账号密码.
            if ("generate_md5_sha1=true".equals(httpExchange.getRequestURI().getQuery())) {
                generateMd5Sha1 = true;
            }
            String authorization = httpExchange.getRequestHeaders().getFirst("Authorization");
            if (null == authorization || !authorization.startsWith("Basic ")) {
                httpExchange.getResponseHeaders().add("WWW-Authenticate","Basic realm=\"maven-manager\"");
                sendResponse(httpExchange, 401, "Unauthorised".getBytes());
                return;
            }
            authorization = authorization.substring(6).trim();
            String[] auth = new String(Base64.getDecoder().decode(authorization)).split(":");
            String user = auth[0];
            String pwd = auth[1];
            if (!store.checkUser(user, pwd)) {
                httpExchange.getResponseHeaders().add("WWW-Authenticate","Basic realm=\"maven-manager\"");
                sendResponse(httpExchange, 401, "Unauthorised".getBytes());
                return;
            }
        }
        if ("put".equalsIgnoreCase(method)) {//put方法处理body内容.
            String contentLength = httpExchange.getRequestHeaders().getFirst("Content-Length");
            data = IoUtil.readToByteBufferByLength(httpExchange.getRequestBody(), Integer.valueOf(contentLength));
            if (null == data || Integer.valueOf(contentLength) != data.length) {
                logger.error("data error, contentLength = {}, data.len = {}, path = {}", contentLength, null != data ? data.length : -1,
                        httpExchange.getRequestURI().getPath());
                sendResponse(httpExchange, 500, "data read failed".getBytes());
                return;
            }
        }
        MavenRequest mavenRequest = new MavenRequest(method, httpExchange.getRequestURI().getPath(), data, generateMd5Sha1);
        MavenResponse mavenResponse = store.handle(mavenRequest);
        if (null != mavenResponse) {
            logger.info("process success, statusCode = {}, request = {} - {}", mavenResponse.getStatusCode(), mavenRequest.getMethod(), mavenRequest.getPath());
            sendResponse(httpExchange, mavenResponse.getStatusCode(), mavenResponse.getBody());
            return;
        }
        logger.info("process failed, request = {} - {}",  mavenRequest.getMethod(), mavenRequest.getPath());
        sendResponse(httpExchange, 404, "404".getBytes());
    }

    private void sendResponse(HttpExchange httpExchange, int statusCode, byte[] responseBody) throws IOException {
        if (null == responseBody) {
            responseBody = new byte[0];
        }
        httpExchange.sendResponseHeaders(statusCode, responseBody.length);
        OutputStream outputStream = httpExchange.getResponseBody();
        outputStream.write(responseBody);
        outputStream.flush();
        outputStream.close();
    }

    public static void main(String[] args) {
        if (!System.getProperties().containsKey("system.start")) {
            System.setProperty("system.start", String.valueOf(System.currentTimeMillis()));
        }
        MavenManagerRunner runner = new MavenManagerRunner();
        if (args.length > 0) {
            runner.config = args[0];
        }
        runner.run();
    }
}
